﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;

namespace ODM.Data.Common
{
    internal static class Extensions
    {

        public static IQueryable<TEntity> Where<TEntity, TValue>(this IQueryable<TEntity> source, Expression<Func<TEntity, TValue, bool>> expr, IEnumerable<TValue> values)
        {
            return source.Where(expr, values, QueryOperator.Or);
        }

        /// <summary>
        /// Filters a sequence of values based on a predicate which runs agains values array
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <typeparam name="TValue">Type of the array value</typeparam>
        /// <param name="source">An <see cref="System.Linq.IQueryable<TEntity>"/> to filter.</param>
        /// <param name="expr">A function to test each element for a condition.</param>
        /// <param name="values">The values to test the predicate against.</param>
        /// <returns>An <see cref="System.Linq.IQueryable<TEntity>"/> that contains elements from the input sequence that satisfy the condition specified by predicate.</returns>
        public static IQueryable<TEntity> Where<TEntity, TValue>(this IQueryable<TEntity> source, Expression<Func<TEntity, TValue, bool>> expr, IEnumerable<TValue> values, QueryOperator logicalOperator)
        {
            IEnumerable<Expression> expressions = null;

            if (values.Count() > 0)
            {
                var p = expr.Parameters[0];

                if (expr.Body is MethodCallExpression)
                {
                    expressions = values.Select(value => (Expression)Expression.Call(
                            ((MethodCallExpression)expr.Body).Object,
                            ((MethodCallExpression)expr.Body).Method,
                            new Expression[] { Expression.Constant(value) })
                        );
                }
                else if (expr.Body is BinaryExpression)
                {
                    MemberExpression left = null;

                    if (((BinaryExpression)expr.Body).Left is MemberExpression && ((BinaryExpression)expr.Body).Right is ParameterExpression)
                    {
                        left = ((BinaryExpression)expr.Body).Left as MemberExpression;
                    }
                    else if (((BinaryExpression)expr.Body).Right is MemberExpression && ((BinaryExpression)expr.Body).Left is ParameterExpression)
                    {
                        left = ((BinaryExpression)expr.Body).Right as MemberExpression;
                    }
                    else
                    {
                        throw new NotSupportedException("Currently BinaryExpression supports only MemberExpression and ParameterExpression data types");
                    }

                    expressions = values.Select(value => (Expression)Expression.Equal(left, Expression.Constant(value, typeof(TValue))));
                }
                else
                {
                    throw new NotSupportedException(string.Format(CultureInfo.CurrentUICulture, "Expression type '{0}' is not supported.", expr.Body.GetType().Name));
                }

                if (expressions != null)
                {
                    Expression body = null;

                    switch (logicalOperator)
                    {
                        case QueryOperator.None:
                            throw new NotSupportedException("LocagialOperator cannot be set to 'None'.");
                        case QueryOperator.And:
                            body = expressions.Aggregate<Expression>((accumulate, equal) => Expression.And(accumulate, equal));
                            break;
                        case QueryOperator.Or:
                            body = expressions.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
                            break;
                    }

                    return source.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
                }
            }

            return source;
        }
    }
}
